/*
 * Copyright (C) 2013 The original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.zapta.apps.maniana.model;

import java.util.Calendar;
import java.util.GregorianCalendar;

import com.zapta.apps.maniana.annotations.ApplicationScope;
import com.zapta.apps.maniana.model.ItemRepeatDay;
import com.zapta.apps.maniana.model.ItemRepeatMonth;
import com.zapta.apps.maniana.util.IdGenerator;

/**
 * Represents the data of a single item.
 *
 * @author Tal Dayan.
 * @author Elliott Mitchell
 */
@ApplicationScope
public class ItemModel implements ItemModelReadOnly {

    /** Globally unique id for this item. Survives item mutations and sync. */
    private final String mId;

    /** System time in millis at the time of creation or last mutation. */
    private long mUpdateTime;

    /** The item text. */
    private String mText;

    /** Extended note text. */
    private String mTextExt;

    /** Is this item done? */
    private int mCompleted;

    /** Due Date for this item */
    private int mDueDate;

    public final static byte SHIFT_YEAR = 9, SHIFT_MONTH = 5, SHIFT_DAY = 0;
    public final static int MASK_YEAR=0x3FFFFF, MASK_MONTH=0xF, MASK_DAY=0x1F;

    /** Show # days early */
    private byte mShowEarly;

    /** The item color. */
    private ItemColor mColor;

    /** color when early */
    private ItemColor mColorEarly;

    /** color when late */
    private ItemColor mColorLate;

    /** Repeat data */
    private ItemRepeat mRepeat;

    /** Normal constructor with initial values. */
    public ItemModel(String text, ItemColor color) {
        mId = IdGenerator.getFreshId();
        mUpdateTime = System.currentTimeMillis();
        mText = text;
        mTextExt = null;
        mCompleted = 0;
        mDueDate = 0;
        mColorLate = mColorEarly = mColor = color;
        mRepeat = null;
    }

    /** Constructor for restoring, allow *all* fields to be set. */
    public ItemModel(long updateTime, String id, String text, String textExt,
int completed, int dueDate, byte showEarly, ItemColor color,
ItemColor colorEarly, ItemColor colorLate, ItemRepeat repeat) {
        mId = id;
        mUpdateTime = updateTime;
        mText = text;
        mTextExt = textExt;
        mCompleted = completed;
        mDueDate = dueDate;
        mShowEarly = showEarly;
        mColor = color;
        mColorEarly = colorEarly;
        mColorLate = colorLate;
        mRepeat = repeat;
    }

    /** Constructor for handling submissions from other programs */
    public ItemModel(StoreIF IF) {
        mId = IF.getString("id");
        fromStore(IF);
    }

    /** Copy constructor. Create an identical but independent instance */
    public ItemModel(ItemModelReadOnly other) {
        mId = other.getId();
        copyFrom(other);
    }

    /** Set to same values as other item. */
    public final void copyFrom(ItemModelReadOnly other) {
        // should we instead simply ignore the id?
        if (!mId.equals(other.getId()))
            throw new RuntimeException("copyFrom() item id doesn't match");
        mUpdateTime = other.getUpdateTime();
        mText = other.getText();
        mTextExt = other.getTextExt();
        mCompleted = other.getCompletionDate();
        mColor = other.getColorDue();
        mColorEarly = other.getColorEarly();
        mColorLate = other.getColorLate();
        mDueDate = other.getDueDate();
        mShowEarly = other.getShowEarly();
        final ItemRepeatReadOnly repeat = other.getRepeatRO();
        if(repeat != null) {
            switch(repeat.getType()) {
	    case DAY:
                mRepeat = new ItemRepeatDay((ItemRepeatDayReadOnly)repeat);
            break;
            case MONTH:
                mRepeat = new ItemRepeatMonth((ItemRepeatMonthReadOnly)repeat);
            break;
            }
        }
    }

    @Override
    public final String getId() {
        return mId;
    }

    @Override
    public final long getUpdateTime() {
        return mUpdateTime;
    }

    @Override
    public final String getText() {
        return mText;
    }

    public final void setText(String text) {
        mText = text;
    }

    @Override
    public final String getTextExt() {
        return mTextExt;
    }

    public final void setTextExt(String textExt) {
        mTextExt = textExt;
    }

    @Override
    public final boolean isCompleted() {
        return mCompleted!=0?true:false;
    }

    public final void setIsCompleted(boolean isCompleted) {
        if(isCompleted) {
            final GregorianCalendar cal=new GregorianCalendar();

            int val=cal.get(Calendar.YEAR)<<SHIFT_YEAR;
            val|=cal.get(Calendar.MONTH)<<SHIFT_MONTH;
            val|=cal.get(Calendar.DAY_OF_MONTH)<<SHIFT_DAY;
            mCompleted=val;
        } else mCompleted = 0;
    }

    @Override
    public final int getCompletionDate() {
        return mCompleted;
    }
    public final void setCompletionDate(int completionDate) {
        mCompleted=completionDate;
    }

    public final int sortingIndex() {
        // lower is closer to the top
        int val = 0;

        // TODO: if(sortingIndex()&0x40000000!=0) => tomorrow page

        if(mDueDate == 0) val|=0x10000000;
        else if(mDueDate>0) {
            if(compareDueDate(((int)mShowEarly)&0xFF)>0) val|=0x50000000;
            val|=mDueDate;
        } else val|=0x4FFFFFFF+mDueDate;
        if(mCompleted!=0) val|=0x20000000;

/* why is this weird sorting order the traditional one?!
        if(mDueDate == -2) val^=3;
        if(mIsCompleted) val^=1;*/

        return val;
    }

    @Override
    public final boolean isLocked() {
        return mDueDate == -2 || mDueDate > 0 ? true : false;
    }

    public final void setIsLocked(boolean isLocked) {
        if(isLocked) {
            mDueDate = -2;
        } else if(mDueDate == -2) mDueDate = -1;
    }

    @Override
    public final PageKind getPage() {
        if(mDueDate>0) return compareDueDate(((int)mShowEarly)&0xFF)<=0?
PageKind.TODAY:PageKind.TOMORROW;
        else return mDueDate==0?PageKind.TODAY:PageKind.TOMORROW;
    }
    public final void setPage(PageKind pageKind) {
        if(mDueDate>0||mDueDate<-1) return; // ignore if date set or locked
        switch(pageKind) {
        case TODAY:
            mDueDate=0;
        break;
        case TOMORROW:
            mDueDate=-1;
        break;
        }
    }

    @Override
    public final int getDueDate() {
        return mDueDate;
    }

    public final void setDueDate(int dueDate) {
        mDueDate = dueDate;
    }

    private final int compareDueDate(int offset) {
        if(mDueDate<=0) return 0;

        // Alas, .compareTo() also looks at time of day
        final GregorianCalendar cal=new GregorianCalendar();
        cal.add(Calendar.DAY_OF_MONTH, offset);

        int val=cal.get(Calendar.YEAR)<<SHIFT_YEAR;
        val|=cal.get(Calendar.MONTH)<<SHIFT_MONTH;
        val|=cal.get(Calendar.DAY_OF_MONTH)<<SHIFT_DAY;
        return mDueDate-val;
    }

    @Override
    public final byte getShowEarly() {
        return mShowEarly;
    }

    public final void setShowEarly(byte days) {
        mShowEarly = days;
    }

    @Override
    public final ItemColor getColor() {
        // NOTE(tal): we assume that the color flag is not needed once an item
        // is completed.  This is a usability heuristic.  Not required
        // otherwise.
        //
        // NOTE(tal): NONE may or may not be in the user selected task color
        // set. It does not matter, we return NONE regardless.
        if(mCompleted!=0) return ItemColor.NONE;

        final int cmp=compareDueDate(0);
        if(cmp<0) return mColorLate;
        if(cmp>0) return mColorEarly;
        return mColor;
    }

    @Override
    public final ItemColor getColorDue() {
        return mColor;
    }

    @Override
    public final ItemColor getColorEarly() {
        return mColorEarly;
    }

    @Override
    public final ItemColor getColorLate() {
        return mColorLate;
    }

    public final void setColor(ItemColor color) {
        final int cmp=compareDueDate(0);
        if(cmp<0) mColorLate=color;
        else if(cmp>0) mColorEarly=color;
        else mColor=color;
    }

    public final void setColorDue(ItemColor color) {
        mColor = color;
    }

    public final void setColorEarly(ItemColor color) {
        mColorEarly = color;
    }

    public final void setColorLate(ItemColor color) {
        mColorLate = color;
    }

    @Override
    public final ItemRepeatReadOnly getRepeatRO() {
        ItemRepeat repeat=this.getRepeat();
        return (ItemRepeatReadOnly)repeat;
    }
    public final ItemRepeat getRepeat() {
        return mRepeat;
    }

    public final void setRepeat(ItemRepeat repeat) {
        mRepeat = repeat;
    }

    public final void mergePropertiesFrom(ItemModelReadOnly other) {
        if(mCompleted>other.getCompletionDate()) mCompleted=
other.getCompletionDate();
        // TODO: should we clear the color if mIsCompleted?
        mColor = mColor.max(other.getColor());
    }

    public interface StoreIF {
        public void setString(final String key, final String value);
        public void setBoolean(final String key, final boolean value);
        public void setByte(final String key, final byte value);
        public void setShort(final String key, final short value);
        public void setInt(final String key, final int value);
        public void setLong(final String key, final long value);

        public String getString(final String key);
        public boolean getBoolean(final String key);
        public byte getByte(final String key);
        public short getShort(final String key);
        public int getInt(final String key);
        public long getLong(final String key);

        public boolean exists(final String key);
        public void delete(final String key);
    }

    @Override
    public void toStore(StoreIF IF) {
        IF.setString("id", mId);
        IF.setLong("utime", mUpdateTime);
        IF.setString("text", mText);
        if(mTextExt!=null&&mTextExt.length()!=0) IF.setString("text_ext", mTextExt);
        if(mCompleted!=0) IF.setInt("completed", mCompleted);
        if(mColor!=ItemColor.NONE) IF.setString("color", mColor.getKey());
        if(mColorEarly!=mColor) IF.setString("color_early", mColorEarly.getKey());
        if(mColorLate!=mColor) IF.setString("color_late", mColorLate.getKey());
        if(mDueDate!=0) IF.setInt("due_date", mDueDate);
        if(mShowEarly!=0) IF.setByte("show_early", mShowEarly);
        if(mRepeat != null) mRepeat.toStore(IF);
    }

    public void fromStore(StoreIF IF) {
        if(IF.getString("id") != null &&
!mId.equals(IF.getString("id"))) throw new
RuntimeException("Store id does not match item_id");

        mUpdateTime = IF.getLong("utime");
        mText = IF.getString("text");
        mTextExt = IF.getString("text_ext");
        mCompleted = IF.exists("done") ? IF.getInt("done") : 0;
        mDueDate = IF.exists("due_date") ? IF.getInt("due_date") : 0;
        mShowEarly = IF.exists("show_early") ? IF.getByte("show_early") : 0;
        mColor = IF.exists("color") ? ItemColor.fromKey(IF.getString("color"),
null) : ItemColor.NONE;
        mColorEarly = !IF.exists("color_early") ? mColor :
ItemColor.fromKey(IF.getString("color_early"), null);
        mColorLate = !IF.exists("color_late") ? mColor :
ItemColor.fromKey(IF.getString("color_late"), null);
        String repeatStr = IF.getString("repeat_type");
        ItemRepeatType repeatType = ItemRepeatType.fromKey(repeatStr);
        switch(repeatType) {
        case NONE:
            mRepeat = null;
        break;
        case DAY:
            mRepeat = new ItemRepeatDay(IF);
        break;
        case MONTH:
            mRepeat = new ItemRepeatMonth(IF);
        break;
        }
    }

    public boolean checkDelete() {
        if(mCompleted==0) return false;
        if(this.mRepeat != null) return mRepeat.checkDelete(this);
        return true;
    }
}
